﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SceneManager
{
    public class Terrain : DrawableObject
    {
        public int Width { get; set; }
        public int Height { get; set; }

        public Vector3 Scale { get; set; }
        public float[,] HeightData { get; set; }

        private VertexPositionNormalTexture[] vertices;
        private int[] indices;

        BasicEffect effect;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        Texture2D texture;

        Material Material { get; set; }

        // TODO: MultiTexturing
        public Texture2D Textures { get; set; }

        public Terrain(Game game, Texture2D heightMap) : base(game)
        {
            Scale = new Vector3(4000, 1000, 4000);//Vector3.One;
            Position = new Vector3(0, -300, 0);

            Width = heightMap.Width;
            Height = heightMap.Height;
            HeightData = new float[Width, Height];

            Color[] heightData = new Color[Width * Height];
            heightMap.GetData<Color>(heightData);

            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                    HeightData[x, y] = heightData[x + y * Width].R / 255f;

            // Creation of vertices
            vertices = new VertexPositionNormalTexture[Width * Height];
            for (int z = 0; z < Height; z++)
                for (int x = 0; x < Width; x++)
                    vertices[x + z * Width] = new VertexPositionNormalTexture(new Vector3((float)x / Width - 0.5f, HeightData[x, z], (float)z / Height - 0.5f), Vector3.Zero, new Vector2((float)x / Width * 32, (float)z / Height * 32));
            
            // Creation of indices
            indices = new int[Width * (Height - 1) * 2];
            int i = 0;
            for (int z = 0; z < Height - 1; z++)
                for (int x = 0; x < Width; x++)
                {
                    if (z % 2 == 0)
                    {
                        indices[i++] = (z + 1) * Width + x;
                        indices[i++] = z * Width + x;
                    }
                    else
                    {
                        indices[i++] = z * Width + (Width - 1 - x);
                        indices[i++] = (z + 1) * Width + (Width - 1 - x);
                    }
                }

            // Creation of normals
            for (int z = 0; z < Height; z++)
                for (int x = 0; x < Width; x++)
                {
                    if (z == 0 || x == 0 || z == Height - 1 || x == Width - 1)
                        vertices[z * Width + x].Normal = Vector3.Up;
                    else
                    {
                        Vector3 o = vertices[z * Width + x].Position;
                        Vector3 n = vertices[(z - 1) * Width + x].Position;
                        Vector3 s = vertices[(z + 1) * Width + x].Position;
                        Vector3 e = vertices[z * Width + (x + 1)].Position;
                        Vector3 w = vertices[z * Width + (x - 1)].Position;

                        vertices[z * Width + x].Normal -= Vector3.Cross(n - o, e - o);
                        vertices[z * Width + x].Normal -= Vector3.Cross(e - o, s - o);
                        vertices[z * Width + x].Normal -= Vector3.Cross(s - o, w - o);
                        vertices[z * Width + x].Normal -= Vector3.Cross(w - o, n - o);

                        vertices[z * Width + x].Normal = Vector3.Normalize(vertices[z * Width + x].Normal); 
                    }
                }
            /*/
            for (i = 0; i < indices.Length; i += 3)
            {
                Vector3 v1 = vertices[indices[i]].Position;
                Vector3 v2 = vertices[indices[i + 1]].Position;
                Vector3 v3 = vertices[indices[i + 2]].Position;

                Vector3 normal = Vector3.Cross(v1 - v2, v1 - v3);
                normal.Normalize();

                vertices[indices[i]].Normal += normal;
                vertices[indices[i + 1]].Normal += normal;
                vertices[indices[i + 2]].Normal += normal;
            }

            for (i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
            //*/

            vertexBuffer = new VertexBuffer(game.GraphicsDevice, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            indexBuffer = new IndexBuffer(game.GraphicsDevice, IndexElementSize.ThirtyTwoBits, indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);

            Material = Materials.Grass;
            //Material.SpecularIntensity = 1;
        }

        public override void Draw(Camera camera, GameTime gameTime)
        {
            Effect.Parameters["World"].SetValue(Matrix.CreateScale(Scale) * Matrix.CreateTranslation(Position));
            Effect.Parameters["View"].SetValue(camera.View);
            Effect.Parameters["Projection"].SetValue(camera.Projection);

            // FIXME
            if (Effect.Parameters.Count(p => p.Name == "Texture") == 1)
                Effect.Parameters["Texture"].SetValue(Material.Texture);
            if (Effect.Parameters.Count(p => p.Name == "SpecularIntensity") == 1)
                Effect.Parameters["SpecularIntensity"].SetValue(Material.SpecularIntensity);
            if (Effect.Parameters.Count(p => p.Name == "SpecularPower") == 1)
                Effect.Parameters["SpecularPower"].SetValue(Material.SpecularPower);

            Effect.CurrentTechnique.Passes[0].Apply();

            Game.GraphicsDevice.Indices = indexBuffer;
            Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, vertexBuffer.VertexCount, 0, indexBuffer.IndexCount);
        }
    }
}
